`timescale 1ns/100ps
`default_nettype none

/* NOTE:
* - 本模负责将已处理好的图像数据存入SDRAM
* - 先写入数据到BRAM，再发起I_write_req
* - 按照BRAM地址最高位做pingpong
* - 每次最多写入256像素
*/

module rgb_pixel_writer (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_sdram_clk, // 150M
    input  wire         I_rst_n,

    // write request
    input  wire         I_write_req,
    output wire         O_write_busy,
    input  wire [20:0]  I_write_addr,
    input  wire [5:0]   I_write_len, // 32bitx63数量
    input  wire         I_write_valid,
    input  wire [23:0]  I_write_data,
    
    // sdram mux
    output wire         O_mux_req,
    input  wire         I_mux_ack,
    input  wire         I_mux_irq, // 中断，表示有更高优先级的模块正在请求SDRAM控制权
    output wire         O_mux_cs_n,
    output wire         O_mux_ras_n,
    output wire         O_mux_cas_n,
    output wire         O_mux_we_n,
    output wire [1:0]   O_mux_ba,
    output wire [10:0]  O_mux_addr,
    output wire [31:0]  O_mux_dq_out,
    input  wire [31:0]  I_mux_dq_in,
    output wire         O_mux_dq_oe,
    output wire [3:0]   O_mux_dqm
);
//------------------------Parameter----------------------
`include "../led_display/sdram_common.vh" 

// fsm
localparam [3:0]
    IDLE  = 0,
    ACT   = 1,
    NOP0  = 2,
    NOP1  = 3,
    WRITE = 4,
    WAIT  = 5,
    LOOP  = 6,
  //  WRITE0 = 7,
 //   WRITE1 = 8,
 //   WRITE2 = 9,
 //   WRITE3 = 10,
    STOP   = 11,
    PRE    = 12,
    WAIT1  = 13,
    LOOP1  = 14;  
    

// wait count
localparam
    WAIT_TIME = (1 << SD_MR_BL) + DELAY_RP - 1;

//------------------------Local signal-------------------
// write request
reg         buf_sel; // pingpong sel
reg  [7:0]  ram_waddr;
reg  [1:0]  write_req_sr;
reg  [2:0]  busy_sr;
reg         pending;
reg         task0_buf;
reg  [20:0] task0_addr;
reg  [6:0]  task0_len;
reg         task1_buf;
reg  [20:0] task1_addr;
reg  [6:0]  task1_len;

// ram
wire        ram_wren;
wire [7:0]  ram_wraddr;
wire [23:0] ram_data;
wire [23:0] ram_q;

// fsm
reg  [3:0]  state;
reg  [3:0]  next;
reg         first_act; // 第一次ACT
reg  [7:0]  total_len;
reg  [2:0]  wait_cnt;

// sdram mux
reg  [1:0]  sdram_req_sr;
wire        sdram_busy;
reg         mux_req;
reg  [3:0]  mux_cmd;
reg  [1:0]  mux_ba;
reg  [20:0] sdram_addr;
reg  [10:0] mux_addr;
reg  [31:0] mux_dq_out;
reg         mux_dq_oe;
reg         write_sr;
reg         ram_rden;
reg  [7:0]  ram_rdaddr;
reg         ram_q_valid;

reg         rd_end;
//------------------------Instantiation------------------
// // sdpram_256x24_256x24
// sdpram_256x24_256x24 ram0 (/*{{{*/
    // .data      ( ram_data ),
    // .rdaddress ( ram_rdaddr ),
    // .rdclock   ( I_sdram_clk ),
    // .rden      ( ram_rden ),
    // .wraddress ( ram_wraddr ),
    // .wrclock   ( I_sclk ),
    // .wren      ( ram_wren ),
    // .q         ( ram_q )
// );/*}}}*/


sdpram_lpm #(
    .A_ADDRESS_WIDTH    (   8   ),
    .A_DATA_WIDTH       (   24   ),
    .B_ADDRESS_WIDTH    (   8   ),
    .B_DATA_WIDTH       (   24   )
    )
ram0(
    .clka       (   I_sclk      ),
    .wea        (   ram_wren    ),
    .addra      (   ram_wraddr   ),
    .dina       (   ram_data    ),

    .clkb       (   I_sclk      ),
    .reb        (   ram_rden    ),
    .addrb      (   ram_rdaddr   ),
    .doutb      (   ram_q       )
    
);


//------------------------Body---------------------------
//{{{+++++++++++++++++++++write request++++++++++++++++++
// NOTE:
// 最多支持两个写入任务
// - 空闲时，收到I_write_req，将任务写入到task0
// - 处理任务时，收到I_write_req，将任务写入到task1，并设置pending标志

assign O_write_busy = pending;

// buf_sel
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        buf_sel <= 1'b0;
    else if (I_write_req & ~pending)
        buf_sel <= ~buf_sel;
end


// ram_waddr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_waddr <= 1'b0;
    else if (I_write_req)
        ram_waddr <= {buf_sel,7'b0};
    else if (I_write_valid) begin
        ram_waddr <= ram_waddr + 1'b1;
    end
end

// write_req_sr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        write_req_sr <= 1'b0;
    else if (I_write_req & ~busy_sr[1])
        write_req_sr <= 2'b11;
    else if (busy_sr[2:1] == 2'b10 && pending)
        write_req_sr <= 2'b11;
    else
        write_req_sr <= write_req_sr << 1;
end

// busy_sr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        busy_sr <= 1'b0;
    else
        busy_sr <= {busy_sr[1:0], sdram_busy};
end

// pending
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        pending <= 1'b0;
    else if (I_write_req & busy_sr[1])
        pending <= 1'b1;
    else if (busy_sr[2:1] == 2'b10)
        pending <= 1'b0;
end

// task0_*
always @(posedge I_sclk) begin
    if (I_write_req & ~busy_sr[1]) begin
        task0_buf  <= buf_sel;
        task0_addr <= I_write_addr;
        task0_len  <= {1'b0,I_write_len};
    end
    else if (busy_sr[2:1] == 2'b10 && pending) begin
        task0_buf  <= task1_buf;
        task0_addr <= task1_addr;
        task0_len  <= task1_len;
    end
end

// task1_*
always @(posedge I_sclk) begin
    if (I_write_req & busy_sr[1]) begin
        task1_buf  <= buf_sel;
        task1_addr <= I_write_addr;
        task1_len  <= I_write_len;
    end
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++ram++++++++++++++++++++++++++++
assign ram_wren   = I_write_valid;
assign ram_wraddr = ram_waddr;
assign ram_data   = I_write_data;

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
// state
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            if (sdram_req_sr[1])
                next = ACT;
            else
                next = IDLE;
        end

        ACT: begin
            if (!first_act)
                next = NOP0;
            else if (I_mux_ack)
                next = NOP1;
            else
                next = ACT;
        end

        NOP0: begin
            next = NOP1; 
        end

        NOP1: begin
            next = WRITE;
        end

        WRITE: begin
            if (total_len == 1'b1)
                next = WAIT;
            else if ( I_mux_irq )
                next = WAIT;
            else
                next = ACT;
        end

        WAIT: begin
            if (wait_cnt >= WAIT_TIME)
                next = LOOP;
            else 
                next = WAIT;
        end

        LOOP: begin
            if (total_len > 1'b0)
                next = ACT;
            else
                next = IDLE;
        end
        
        default: begin
            next = IDLE;
        end
    endcase
end

always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        rd_end <= 1'b0;
    else if (state == WRITE && ( I_mux_irq || total_len == 1'b1)  )
        rd_end <= 1'b1;
    else if( state == WRITE )
        rd_end <= 1'b0;
end


// first_act
always @(posedge I_sdram_clk) begin
    if (state == IDLE || state == LOOP)
        first_act <= 1'b1;
    else if (state == WRITE)
        first_act <= 1'b0;
end

// total_len
always @(posedge I_sdram_clk) begin
    if (state == IDLE && next == ACT)
        total_len <= task0_len; // NOTE: 设置false path
    else if (state == WRITE)
        total_len <= total_len - 1'b1;
end

// wait_cnt
always @(posedge I_sdram_clk) begin
    if (state == WRITE)
        wait_cnt <= 1'b0;
    else
        wait_cnt <= wait_cnt + 1'b1;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++sdram mux++++++++++++++++++++++
assign O_mux_req    = mux_req;
assign O_mux_cs_n   = mux_cmd[3];
assign O_mux_ras_n  = mux_cmd[2];
assign O_mux_cas_n  = mux_cmd[1];
assign O_mux_we_n   = mux_cmd[0];
assign O_mux_ba     = mux_ba;
assign O_mux_addr   = mux_addr;
assign O_mux_dq_out = mux_dq_out;
assign O_mux_dq_oe  = mux_dq_oe;
assign O_mux_dqm    = 1'b0;

assign sdram_busy = (state != IDLE);


// sdram_req_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        sdram_req_sr <= 1'b0;
    else
        sdram_req_sr <= {sdram_req_sr[0], write_req_sr[1]};
end

// mux_req
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_req <= 1'b0;
    else if (state == ACT)
        mux_req <= 1'b1;
    else if (state == LOOP)
        mux_req <= 1'b0;
end

// mux_cmd
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_cmd <= SD_CMD_NOP;
    else if (state == ACT) begin
        if (first_act & I_mux_ack)
            mux_cmd <= SD_CMD_NOP;
        else
            mux_cmd <= SD_CMD_ACT;
    end
    else if (state == WRITE)
        mux_cmd <= SD_CMD_WRIT;
    else
        mux_cmd <= SD_CMD_NOP;
end


// mux_ba
always @(posedge I_sdram_clk) begin
    // if (state == IDLE && next == ACT)
        // mux_ba <= task0_addr[9:8]; // NOTE: 设置false path
    // else 
    if ( state == ACT ) 
        mux_ba <= sdram_addr[9:8];
end

// sdram_addr
always @(posedge I_sdram_clk) begin
    if (state == IDLE && next == ACT)
        sdram_addr <= task0_addr; // NOTE: 设置false path
    else if (state == WRITE ) begin
        sdram_addr[16:8] <= sdram_addr[16:8] + 1'b1;
    end 
end

// mux_addr
always @(posedge I_sdram_clk) begin
    if (state == ACT)
        mux_addr <= {sdram_addr[20:10]};
    else if (state == WRITE)
        mux_addr <= {1'b1, 2'd0, sdram_addr[7:0]};
end

// mux_dq_out
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_dq_out <= 1'b0;
    else if (ram_q_valid)
        mux_dq_out <= {8'b0 , ram_q }/*32'h00000000*/;
end

// mux_dq_oe
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_dq_oe <= 1'b0;
    else
        mux_dq_oe <= ram_q_valid;
end

// write_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        write_sr <= 1'b0;
    else
        write_sr <= (state == WRITE);
end

// ram_rden
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_rden <= 1'b0;
    else if (state == ACT && next == NOP1)
        ram_rden <= 1'b1;
    else if (state == NOP0)
        ram_rden <= 1'b1;
    else if (state == WAIT && wait_cnt == 1'd1)
        ram_rden <= 1'b0;
end

// ram_rdaddr
always @(posedge I_sdram_clk) begin
    if (state == IDLE)
        ram_rdaddr <= {task0_buf, 7'd0};
    else if (ram_rden)
        ram_rdaddr <= ram_rdaddr + 1'b1;
end

// ram_q_valid
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_q_valid <= 1'b0;
    else
        ram_q_valid <= ram_rden;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++


endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
